//==============================================================================
//
//           DDbm - the database manager class in the ofc-library
//
//               Copyright (C) 2003  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2005-01-31 06:44:30 $ $Revision: 1.7 $
//
//==============================================================================

#include "ofc/DDbm.h"

#include <stdlib.h>

#ifdef HAVE_DDBM
#include <gdbm.h>
#endif

#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DList.h"
#include "ofc/DData.h"

#if defined(HAVE_LIBGDBM) && defined(HAVE_GDBM_H)
#define HAVE_DDBM
#endif

#ifdef HAVE_DDBM

// The Dbm class implements a wrapper around the gdbm database manager.
// The naming of the methods is identical to the hashtable class, but this
// class is not a collection. It stores data, not objects. Open modes: r = read, 
// w = write, n = write in new database, c = write in new database if not exist.
//

@interface DDbm : Object
{
@private
  void        *_dbf;   // the database
  char         _mode;  // the database mode
}
#endif

#endif



#ifdef HAVE_DDBM


@implementation DDbm


//// Constructors

//
// Initialise without a database
//
// @return the object
//

- (DDbm *) init
{
  [super init];

  _dbf   = NULL;
  _mode  = EOS;
  
  return self;
}

//
// Initialise with a database
// 
// @param name     the database name
// @param mode     the mode for the database (r,w,n,c)
// 
// @return the object
// 

- (DDbm *) init :(const char *) name :(const char *) mode
{
  [self init];
  
  [self open :name :mode];
  
  return self;
}


//// Deconstructor

//
// Free the object
// 

- free
{
  if (_dbf != NULL)
  {
    [self close];
  }
  
  [super free];
  
  return self;
}

//// Copy related methods

//
// Deepen the object (not implemented)
//
//

- deepen
{
  WARNING(DW_METHOD_NOT_IMPL, "deepen");
  
  return self;
}


//// Member methods

//
// Check if the database is open
//
// @return is it ?
//

- (BOOL) isOpen
{
  return (_dbf != NULL);
}

//
// Check if the the database is read only ?
//
// @return is it ?
//

- (BOOL) isReadOnly
{
  if (_dbf == NULL)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
    
    return NO;
  }
  
  return (_mode == 'r');
}

//
// Return the last error
//
// @return the last error
//

- (int) error
{
  return (int) gdbm_errno;
}


//// Main methods

//
// Open a database
//
// @param name     the name of the database
// @param mode     the mode for the database (r,w,c,n)
//
// @return is the dbm open ?
//

- (BOOL) open :(const char *) name :(const char *) mode
{
  int dmode = -1;

  if ((name == NULL) || (strlen(name) == 0))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if ((mode == NULL) || (strlen(mode) != 1))
  {
    WARNING(DW_INVALID_ARG, "mode");
  }
  else
  {
    _mode = mode[0];
  
    switch(_mode)
    {
      case 'r' : dmode = GDBM_READER;  break;
      case 'w' : dmode = GDBM_WRITER;  break;
      case 'n' : dmode = GDBM_NEWDB;   break;
      case 'c' : dmode = GDBM_WRCREAT; break;
      default  : dmode = -1;
    }
  
    if (dmode != -1)
    {
      if (_dbf != NULL)
      {
        [self close];
      }
      
 #ifdef WIN32
      dmode |= GDBM_NOLOCK;
 #endif
      
      _dbf = gdbm_open((char *) name, 0, dmode, 0666, 0);
    }
    else
    {
      WARNING(DW_INVALID_ARG, "mode");
    }
  }
  
  return (_dbf != NULL);
}

//
// Insert data in the database (only writer)
// 
// @param key      the key
// @param klen     the length of the key
// @param data     the data
// @param dlen     the length of the data
// 
// @return success
//

- (BOOL) insert :(void *) key :(unsigned) klen :(void *) data :(unsigned) dlen
{
  BOOL ok = NO;

  if (key == NULL)
    WARNING(DW_INVALID_ARG, "key");
  else if (klen == 0)
    WARNING(DW_INVALID_ARG, "klen");
  else if (data == NULL)
    WARNING(DW_INVALID_ARG, "data");
  else if (dlen == 0)
    WARNING(DW_INVALID_ARG, "dlen");
  else if (_dbf == NULL)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    datum dkey, ddata;
    
    dkey.dptr   = key;
    dkey.dsize  = klen;
    
    ddata.dptr  = data;
    ddata.dsize = dlen;
    
    ok = (gdbm_store(_dbf, dkey, ddata, GDBM_REPLACE) == 0);
  }
  
  return ok;
}

//
// Return the data related to a key
// 
// @param key      the key
// @param klen     the length of the key
// 
// @return the data in a new DData object (or nil if not found)
// 

- (DData *) get :(void *) key :(unsigned) klen
{
  DData *data = nil;

  if (key == NULL)
    WARNING(DW_INVALID_ARG, "key");
  else if (klen == 0)
    WARNING(DW_INVALID_ARG, "klen");
  else if (_dbf == NULL)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    datum dkey,ddata;
    
    dkey.dptr  = key;
    dkey.dsize = klen;
    
    ddata = gdbm_fetch(_dbf, dkey);
    
    if (ddata.dptr != NULL)
    {
      data = [DData alloc];
      
      [data init :ddata.dptr :ddata.dsize];
      
      free(ddata.dptr);
    }
  }
  
  return data;
}

//
// Check if the database has a key
// 
// @param key      the key
// @param klen     the length of the key
// 
// @return is the key present ?
//

- (BOOL) has :(void *) key :(unsigned) klen
{
  BOOL ok = NO;

  if (key == NULL)
    WARNING(DW_INVALID_ARG, "key");
  else if (klen <= 0)
    WARNING(DW_INVALID_ARG, "klen");
  else if (_dbf == NULL)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    datum dkey;
    
    dkey.dptr  = key;
    dkey.dsize = klen;
    
    ok = gdbm_exists(_dbf, dkey);
  }
  
  return ok;
}

//
// Delete a key,data from the database (only writer)
// 
// @param key      the key
// @param klen     the length of the key
// 
// @return success
//

- (BOOL) delete :(void *) key :(unsigned) klen
{
  BOOL ok = NO;
  
  if (key == NULL)
    WARNING(DW_INVALID_ARG, "key");
  else if (klen <= 0)
    WARNING(DW_INVALID_ARG, "klen");
  else if (_dbf == NULL)
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  else
  {
    datum dkey;
    
    dkey.dptr  = key;
    dkey.dsize = klen;
    
    ok = (gdbm_delete(_dbf, dkey) == 0);
  }
  
  return ok;
}


//
// Reorganize the database (only writer)
//
// @return the object
//

- (BOOL) reorganize
{
  BOOL ok = NO;
  
  if (_dbf != NULL)
  {
    ok = (gdbm_reorganize(_dbf) == 0);
  }
  
  return ok;
}

//
// Close the database
// 
// @return the object
// 

- (DDbm *) close
{
  if (_dbf != NULL)
  {
    gdbm_close(_dbf);

    _dbf = NULL;
  }
  
  return self;
}

//// List related methods

//
// Return a list with all the keys in the database
// 
// @return the (new) list with all the keys in DData objects (or nil for error)
// 

- (DList *) keys
{
  DList *list = nil;
  
  if (_dbf != NULL)
  {
    datum dkey, dnext;

    list = [DList new];
    
    dkey = gdbm_firstkey(_dbf);
    
    while (dkey.dptr != NULL)
    {
      DData *data = [DData alloc];
      
      [data init :dkey.dptr :dkey.dsize];
      
      [list append :data];
      
      dnext = gdbm_nextkey(_dbf, dkey);
      
      free(dkey.dptr);
      
      dkey = dnext;
    }
  }
  else
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  
  return list;
}

//
// Return a list with all the data in the database
// 
// @return the (new) list with all the data in DData objects (or nil for error)
// 

- (DList *) objects
{
  DList *list = nil;
  
  if (_dbf != NULL)
  {
    datum dkey, dnext,ddata;

    list = [DList new];
    
    dkey = gdbm_firstkey(_dbf);
    
    while (dkey.dptr != NULL)
    {
      ddata = gdbm_fetch(_dbf, dkey);
      
      if (ddata.dptr != NULL)
      {
        DData *data = [DData alloc];
      
        [data init :dkey.dptr :dkey.dsize];
      
        [list append :data];
        
        free(ddata.dptr);
      }
      dnext = gdbm_nextkey(_dbf, dkey);
      
      free(dkey.dptr);
      
      dkey = dnext;
    }
  }
  else
  {
    WARNING(DW_OBJECT_NOT_INIT, "open");
  }
  
  return list;
}

@end
#endif

/*===========================================================================*/

